//-----------------------------------------------------------------------------
// File: Joystick.cpp
//
// Desc: Demonstrates an application which receives immediate 
//       joystick data in exclusive mode via a dialog timer.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#define STRICT
#define DIRECTINPUT_VERSION 0x0800
#define _CRT_SECURE_NO_DEPRECATE
#ifndef _WIN32_DCOM
#define _WIN32_DCOM
#endif

#include <windows.h>
#include <commctrl.h>
#include <basetsd.h>
#include <dinput.h>
#include <dinputd.h>
#include <assert.h>
#include <oleauto.h>
#include <shellapi.h>

#pragma warning( disable : 4996 ) // disable deprecated warning 

#include <strsafe.h>

#pragma warning( default : 4996 )

#include "resource.h"


//-----------------------------------------------------------------------------
// Function-prototypes
//-----------------------------------------------------------------------------
INT_PTR CALLBACK MainDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

BOOL CALLBACK EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE *pdidoi, VOID *pContext);

BOOL CALLBACK EnumJoysticksCallback(const DIDEVICEINSTANCE *pdidInstance, VOID *pContext);

HRESULT InitDirectInput(HWND hDlg);

VOID FreeDirectInput();

HRESULT UpdateInputState(HWND hDlg);

// Stuff to filter out XInput devices
#include <wbemidl.h>

HRESULT SetupForIsXInputDevice();

bool IsXInputDevice(const GUID *pGuidProductFromDirectInput);

void CleanupForIsXInputDevice();

struct XINPUT_DEVICE_NODE {
    DWORD dwVidPid;
    XINPUT_DEVICE_NODE *pNext;
};

struct DI_ENUM_CONTEXT {
    DIJOYCONFIG *pPreferredJoyCfg;
    bool bPreferredJoyCfgValid;
};

bool g_bFilterOutXinputDevices = false;
XINPUT_DEVICE_NODE *g_pXInputDeviceList = NULL;




//-----------------------------------------------------------------------------
// Defines, constants, and global variables
//-----------------------------------------------------------------------------
#define SAFE_DELETE(p)  { if(p) { delete (p);     (p)=NULL; } }
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }

LPDIRECTINPUT8 g_pDI = NULL;
LPDIRECTINPUTDEVICE8 g_pJoystick = NULL;


//-----------------------------------------------------------------------------
// Name: WinMain()
// Desc: Entry point for the application.  Since we use a simple dialog for 
//       user interaction we don't need to pump messages.
//-----------------------------------------------------------------------------
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int) {
    InitCommonControls();

    WCHAR *strCmdLine;
    int nNumArgs;
    LPWSTR *pstrArgList = CommandLineToArgvW(GetCommandLineW(), &nNumArgs);
    for (int iArg = 1; iArg < nNumArgs; iArg++) {
        strCmdLine = pstrArgList[iArg];

        // Handle flag args
        if (*strCmdLine == L'/' || *strCmdLine == L'-') {
            strCmdLine++;

            int nArgLen = (int) wcslen(L"noxinput");
            if (_wcsnicmp(strCmdLine, L"noxinput", nArgLen) == 0 && strCmdLine[nArgLen] == 0) {
                g_bFilterOutXinputDevices = true;
                continue;
            }
        }
    }
    LocalFree(pstrArgList);

    // Display the main dialog box.
    DialogBox(hInst, MAKEINTRESOURCE(IDD_JOYST_IMM), NULL, MainDlgProc);

    return 0;
}


//-----------------------------------------------------------------------------
// Name: MainDialogProc
// Desc: Handles dialog messages
//-----------------------------------------------------------------------------
INT_PTR CALLBACK MainDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) {
    UNREFERENCED_PARAMETER(lParam);

    switch (msg) {
        case WM_INITDIALOG:
            if (FAILED(InitDirectInput(hDlg))) {
                MessageBox(NULL, TEXT("Error Initializing DirectInput"),
                           TEXT("DirectInput Sample"), MB_ICONERROR | MB_OK);
                EndDialog(hDlg, 0);
            }

            // Set a timer to go off 30 times a second. At every timer message
            // the input device will be read
            SetTimer(hDlg, 0, 1000 / 30, NULL);
            return TRUE;

        case WM_ACTIVATE:
            if (WA_INACTIVE != wParam && g_pJoystick) {
                // Make sure the device is acquired, if we are gaining focus.
                g_pJoystick->Acquire();
            }
            return TRUE;

        case WM_TIMER:
            // Update the input device every timer message
            if (FAILED(UpdateInputState(hDlg))) {
                KillTimer(hDlg, 0);
                MessageBox(NULL, TEXT("Error Reading Input State. ") \
                            TEXT("The sample will now exit."), TEXT("DirectInput Sample"),
                           MB_ICONERROR | MB_OK);
                EndDialog(hDlg, TRUE);
            }
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDCANCEL:
                    EndDialog(hDlg, 0);
                    return TRUE;
            }

        case WM_DESTROY:
            // Cleanup everything
            KillTimer(hDlg, 0);
            FreeDirectInput();
            return TRUE;
    }

    return FALSE; // Message not handled 
}


//-----------------------------------------------------------------------------
// Name: InitDirectInput()
// Desc: Initialize the DirectInput variables.
//-----------------------------------------------------------------------------
HRESULT InitDirectInput(HWND hDlg) {
    HRESULT hr;

    // Register with the DirectInput subsystem and get a pointer
    // to a IDirectInput interface we can use.
    // Create a DInput object
    if (FAILED(hr = DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION,
                                       IID_IDirectInput8, (VOID * *) & g_pDI, NULL)))
        return hr;


    if (g_bFilterOutXinputDevices)
        SetupForIsXInputDevice();

    DIJOYCONFIG PreferredJoyCfg = {0};
    DI_ENUM_CONTEXT enumContext;
    enumContext.pPreferredJoyCfg = &PreferredJoyCfg;
    enumContext.bPreferredJoyCfgValid = false;

    IDirectInputJoyConfig8 *pJoyConfig = NULL;
    if (FAILED(hr = g_pDI->QueryInterface(IID_IDirectInputJoyConfig8, (void **) &pJoyConfig)))
        return hr;

    PreferredJoyCfg.dwSize = sizeof(PreferredJoyCfg);
    if (SUCCEEDED(pJoyConfig->GetConfig(0, &PreferredJoyCfg,
                                        DIJC_GUIDINSTANCE))) // This function is expected to fail if no joystick is attached
        enumContext.bPreferredJoyCfgValid = true;
    SAFE_RELEASE(pJoyConfig);

    // Look for a simple joystick we can use for this sample program.
    if (FAILED(hr = g_pDI->EnumDevices(DI8DEVCLASS_GAMECTRL,
                                       EnumJoysticksCallback,
                                       &enumContext, DIEDFL_ATTACHEDONLY)))
        return hr;

    if (g_bFilterOutXinputDevices)
        CleanupForIsXInputDevice();

    // Make sure we got a joystick
    if (NULL == g_pJoystick) {
        MessageBox(NULL, TEXT("Joystick not found. The sample will now exit."),
                   TEXT("DirectInput Sample"),
                   MB_ICONERROR | MB_OK);
        EndDialog(hDlg, 0);
        return S_OK;
    }

    // Set the data format to "simple joystick" - a predefined data format 
    //
    // A data format specifies which controls on a device we are interested in,
    // and how they should be reported. This tells DInput that we will be
    // passing a DIJOYSTATE2 structure to IDirectInputDevice::GetDeviceState().
    if (FAILED(hr = g_pJoystick->SetDataFormat(&c_dfDIJoystick2)))
        return hr;

    // Set the cooperative level to let DInput know how this device should
    // interact with the system and with other DInput applications.
    if (FAILED(hr = g_pJoystick->SetCooperativeLevel(hDlg, DISCL_EXCLUSIVE |
                                                           DISCL_FOREGROUND)))
        return hr;

    // Enumerate the joystick objects. The callback function enabled user
    // interface elements for objects that are found, and sets the min/max
    // values property for discovered axes.
    if (FAILED(hr = g_pJoystick->EnumObjects(EnumObjectsCallback,
                                             (VOID *) hDlg, DIDFT_ALL)))
        return hr;

    return S_OK;
}


//-----------------------------------------------------------------------------
// Enum each PNP device using WMI and check each device ID to see if it contains 
// "IG_" (ex. "VID_045E&PID_028E&IG_00").  If it does, then it抯 an XInput device
// Unfortunately this information can not be found by just using DirectInput.
// Checking against a VID/PID of 0x028E/0x045E won't find 3rd party or future
// XInput devices.
//
// This function stores the list of xinput devices in a linked list
// at g_pXInputDeviceList, and IsXInputDevice() searchs that linked list
//-----------------------------------------------------------------------------
HRESULT SetupForIsXInputDevice() {
    IWbemServices *pIWbemServices = NULL;
    IEnumWbemClassObject *pEnumDevices = NULL;
    IWbemLocator *pIWbemLocator = NULL;
    IWbemClassObject *pDevices[20] = {0};
    BSTR bstrDeviceID = NULL;
    BSTR bstrClassName = NULL;
    BSTR bstrNamespace = NULL;
    DWORD uReturned = 0;
    bool bCleanupCOM = false;
    UINT iDevice = 0;
    VARIANT var;
    HRESULT hr;

    // CoInit if needed
    hr = CoInitialize(NULL);
    bCleanupCOM = SUCCEEDED(hr);

    // Create WMI
    hr = CoCreateInstance(__uuidof(WbemLocator),
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          __uuidof(IWbemLocator),
                          (LPVOID *) &pIWbemLocator);
    if (FAILED(hr) || pIWbemLocator == NULL)
        goto LCleanup;

    // Create BSTRs for WMI
    bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2");
    if (bstrNamespace == NULL) goto LCleanup;
    bstrDeviceID = SysAllocString(L"DeviceID");
    if (bstrDeviceID == NULL) goto LCleanup;
    bstrClassName = SysAllocString(L"Win32_PNPEntity");
    if (bstrClassName == NULL) goto LCleanup;

    // Connect to WMI
    hr = pIWbemLocator->ConnectServer(bstrNamespace, NULL, NULL, 0L,
                                      0L, NULL, NULL, &pIWbemServices);
    if (FAILED(hr) || pIWbemServices == NULL)
        goto LCleanup;

    // Switch security level to IMPERSONATE
    CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
                      RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, 0);

    // Get list of Win32_PNPEntity devices
    hr = pIWbemServices->CreateInstanceEnum(bstrClassName, 0, NULL, &pEnumDevices);
    if (FAILED(hr) || pEnumDevices == NULL)
        goto LCleanup;

    // Loop over all devices
    for (;;) {
        // Get 20 at a time
        hr = pEnumDevices->Next(10000, 20, pDevices, &uReturned);
        if (FAILED(hr))
            goto LCleanup;
        if (uReturned == 0)
            break;

        for (iDevice = 0; iDevice < uReturned; iDevice++) {
            // For each device, get its device ID
            hr = pDevices[iDevice]->Get(bstrDeviceID, 0L, &var, NULL, NULL);
            if (SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != NULL) {
                // Check if the device ID contains "IG_".  If it does, then it抯 an XInput device
                // Unfortunately this information can not be found by just using DirectInput 
                if (wcsstr(var.bstrVal, L"IG_")) {
                    // If it does, then get the VID/PID from var.bstrVal
                    DWORD dwPid = 0, dwVid = 0;
                    WCHAR *strVid = wcsstr(var.bstrVal, L"VID_");
                    if (strVid && swscanf(strVid, L"VID_%4X", &dwVid) != 1)
                        dwVid = 0;
                    WCHAR *strPid = wcsstr(var.bstrVal, L"PID_");
                    if (strPid && swscanf(strPid, L"PID_%4X", &dwPid) != 1)
                        dwPid = 0;

                    DWORD dwVidPid = MAKELONG(dwVid, dwPid);

                    // Add the VID/PID to a linked list
                    XINPUT_DEVICE_NODE *pNewNode = new XINPUT_DEVICE_NODE;
                    if (pNewNode) {
                        pNewNode->dwVidPid = dwVidPid;
                        pNewNode->pNext = g_pXInputDeviceList;
                        g_pXInputDeviceList = pNewNode;
                    }
                }
            }
            SAFE_RELEASE(pDevices[iDevice]);
        }
    }

    LCleanup:
    if (bstrNamespace)
        SysFreeString(bstrNamespace);
    if (bstrDeviceID)
        SysFreeString(bstrDeviceID);
    if (bstrClassName)
        SysFreeString(bstrClassName);
    for (iDevice = 0; iDevice < 20; iDevice++) SAFE_RELEASE(pDevices[iDevice]);
    SAFE_RELEASE(pEnumDevices);
    SAFE_RELEASE(pIWbemLocator);
    SAFE_RELEASE(pIWbemServices);

    return hr;
}


//-----------------------------------------------------------------------------
// Returns true if the DirectInput device is also an XInput device.
// Call SetupForIsXInputDevice() before, and CleanupForIsXInputDevice() after
//-----------------------------------------------------------------------------
bool IsXInputDevice(const GUID *pGuidProductFromDirectInput) {
    // Check each xinput device to see if this device's vid/pid matches
    XINPUT_DEVICE_NODE *pNode = g_pXInputDeviceList;
    while (pNode) {
        if (pNode->dwVidPid == pGuidProductFromDirectInput->Data1)
            return true;
        pNode = pNode->pNext;
    }

    return false;
}


//-----------------------------------------------------------------------------
// Cleanup needed for IsXInputDevice()
//-----------------------------------------------------------------------------
void CleanupForIsXInputDevice() {
    // Cleanup linked list
    XINPUT_DEVICE_NODE *pNode = g_pXInputDeviceList;
    while (pNode) {
        XINPUT_DEVICE_NODE *pDelete = pNode;
        pNode = pNode->pNext;
        SAFE_DELETE(pDelete);
    }
}



//-----------------------------------------------------------------------------
// Name: EnumJoysticksCallback()
// Desc: Called once for each enumerated joystick. If we find one, create a
//       device interface on it so we can play with it.
//-----------------------------------------------------------------------------
BOOL CALLBACK EnumJoysticksCallback(const DIDEVICEINSTANCE *pdidInstance,
                                    VOID *pContext) {
    DI_ENUM_CONTEXT *pEnumContext = (DI_ENUM_CONTEXT *) pContext;
    HRESULT hr;

    if (g_bFilterOutXinputDevices && IsXInputDevice(&pdidInstance->guidProduct))
        return DIENUM_CONTINUE;

    // Skip anything other than the perferred joystick device as defined by the control panel.  
    // Instead you could store all the enumerated joysticks and let the user pick.
    if (pEnumContext->bPreferredJoyCfgValid &&
        !IsEqualGUID(pdidInstance->guidInstance, pEnumContext->pPreferredJoyCfg->guidInstance))
        return DIENUM_CONTINUE;

    // Obtain an interface to the enumerated joystick.
    hr = g_pDI->CreateDevice(pdidInstance->guidInstance, &g_pJoystick, NULL);

    // If it failed, then we can't use this joystick. (Maybe the user unplugged
    // it while we were in the middle of enumerating it.)
    if (FAILED(hr))
        return DIENUM_CONTINUE;

    // Stop enumeration. Note: we're just taking the first joystick we get. You
    // could store all the enumerated joysticks and let the user pick.
    return DIENUM_STOP;
}




//-----------------------------------------------------------------------------
// Name: EnumObjectsCallback()
// Desc: Callback function for enumerating objects (axes, buttons, POVs) on a 
//       joystick. This function enables user interface elements for objects
//       that are found to exist, and scales axes min/max values.
//-----------------------------------------------------------------------------
BOOL CALLBACK EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE *pdidoi,
                                  VOID *pContext) {
    HWND hDlg = (HWND) pContext;

    static int nSliderCount = 0;  // Number of returned slider controls
    static int nPOVCount = 0;     // Number of returned POV controls

    // For axes that are returned, set the DIPROP_RANGE property for the
    // enumerated axis in order to scale min/max values.
    if (pdidoi->dwType & DIDFT_AXIS) {
        DIPROPRANGE diprg;
        diprg.diph.dwSize = sizeof(DIPROPRANGE);
        diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
        diprg.diph.dwHow = DIPH_BYID;
        diprg.diph.dwObj = pdidoi->dwType; // Specify the enumerated axis
        diprg.lMin = -1000;
        diprg.lMax = +1000;

        // Set the range for the axis
        if (FAILED(g_pJoystick->SetProperty(DIPROP_RANGE, &diprg.diph)))
            return DIENUM_STOP;

    }


    // Set the UI to reflect what objects the joystick supports
    if (pdidoi->guidType == GUID_XAxis) {
        EnableWindow(GetDlgItem(hDlg, IDC_X_AXIS), TRUE);
        EnableWindow(GetDlgItem(hDlg, IDC_X_AXIS_TEXT), TRUE);
    }
    if (pdidoi->guidType == GUID_YAxis) {
        EnableWindow(GetDlgItem(hDlg, IDC_Y_AXIS), TRUE);
        EnableWindow(GetDlgItem(hDlg, IDC_Y_AXIS_TEXT), TRUE);
    }
    if (pdidoi->guidType == GUID_ZAxis) {
        EnableWindow(GetDlgItem(hDlg, IDC_Z_AXIS), TRUE);
        EnableWindow(GetDlgItem(hDlg, IDC_Z_AXIS_TEXT), TRUE);
    }
    if (pdidoi->guidType == GUID_RxAxis) {
        EnableWindow(GetDlgItem(hDlg, IDC_X_ROT), TRUE);
        EnableWindow(GetDlgItem(hDlg, IDC_X_ROT_TEXT), TRUE);
    }
    if (pdidoi->guidType == GUID_RyAxis) {
        EnableWindow(GetDlgItem(hDlg, IDC_Y_ROT), TRUE);
        EnableWindow(GetDlgItem(hDlg, IDC_Y_ROT_TEXT), TRUE);
    }
    if (pdidoi->guidType == GUID_RzAxis) {
        EnableWindow(GetDlgItem(hDlg, IDC_Z_ROT), TRUE);
        EnableWindow(GetDlgItem(hDlg, IDC_Z_ROT_TEXT), TRUE);
    }
    if (pdidoi->guidType == GUID_Slider) {
        switch (nSliderCount++) {
            case 0 :
                EnableWindow(GetDlgItem(hDlg, IDC_SLIDER0), TRUE);
                EnableWindow(GetDlgItem(hDlg, IDC_SLIDER0_TEXT), TRUE);
                break;

            case 1 :
                EnableWindow(GetDlgItem(hDlg, IDC_SLIDER1), TRUE);
                EnableWindow(GetDlgItem(hDlg, IDC_SLIDER1_TEXT), TRUE);
                break;
        }
    }
    if (pdidoi->guidType == GUID_POV) {
        switch (nPOVCount++) {
            case 0 :
                EnableWindow(GetDlgItem(hDlg, IDC_POV0), TRUE);
                EnableWindow(GetDlgItem(hDlg, IDC_POV0_TEXT), TRUE);
                break;

            case 1 :
                EnableWindow(GetDlgItem(hDlg, IDC_POV1), TRUE);
                EnableWindow(GetDlgItem(hDlg, IDC_POV1_TEXT), TRUE);
                break;

            case 2 :
                EnableWindow(GetDlgItem(hDlg, IDC_POV2), TRUE);
                EnableWindow(GetDlgItem(hDlg, IDC_POV2_TEXT), TRUE);
                break;

            case 3 :
                EnableWindow(GetDlgItem(hDlg, IDC_POV3), TRUE);
                EnableWindow(GetDlgItem(hDlg, IDC_POV3_TEXT), TRUE);
                break;
        }
    }

    return DIENUM_CONTINUE;
}


//-----------------------------------------------------------------------------
// Name: UpdateInputState()
// Desc: Get the input device's state and display it.
//-----------------------------------------------------------------------------
HRESULT UpdateInputState(HWND hDlg) {
    HRESULT hr;
    TCHAR strText[512] = {0}; // Device state text
    DIJOYSTATE2 js;           // DInput joystick state 

    if (NULL == g_pJoystick)
        return S_OK;

    // Poll the device to read the current state
    hr = g_pJoystick->Poll();
    if (FAILED(hr)) {
        // DInput is telling us that the input stream has been
        // interrupted. We aren't tracking any state between polls, so
        // we don't have any special reset that needs to be done. We
        // just re-acquire and try again.
        hr = g_pJoystick->Acquire();
        while (hr == DIERR_INPUTLOST)
            hr = g_pJoystick->Acquire();

        // hr may be DIERR_OTHERAPPHASPRIO or other errors.  This
        // may occur when the app is minimized or in the process of 
        // switching, so just try again later 
        return S_OK;
    }

    // Get the input's device state
    if (FAILED(hr = g_pJoystick->GetDeviceState(sizeof(DIJOYSTATE2), &js)))
        return hr; // The device should have been acquired during the Poll()

    // Display joystick state to dialog

    /**
     * AXIS.XY: 左摇杆
     *  lX,lY
     * ROTATION.XT:右摇杆
     *  lRx,lRy
     * Z_AXIS:LR2
     *  lZ
     * POV0:十字方向键
     *  rgdwPOV[0]
     * BUTTONS:除上面外的其他物理键,包括按下摇杆,支持多键同时按下
     *  rgbButtons[0,127]
     */
    // Axes
    StringCchPrintf(strText, 512, TEXT("%ld"), js.lX);
    SetWindowText(GetDlgItem(hDlg, IDC_X_AXIS), strText);
    StringCchPrintf(strText, 512, TEXT("%ld"), js.lY);
    SetWindowText(GetDlgItem(hDlg, IDC_Y_AXIS), strText);
    StringCchPrintf(strText, 512, TEXT("%ld"), js.lZ);
    SetWindowText(GetDlgItem(hDlg, IDC_Z_AXIS), strText);
    StringCchPrintf(strText, 512, TEXT("%ld"), js.lRx);
    SetWindowText(GetDlgItem(hDlg, IDC_X_ROT), strText);
    StringCchPrintf(strText, 512, TEXT("%ld"), js.lRy);
    SetWindowText(GetDlgItem(hDlg, IDC_Y_ROT), strText);
    StringCchPrintf(strText, 512, TEXT("%ld"), js.lRz);
    SetWindowText(GetDlgItem(hDlg, IDC_Z_ROT), strText);

    // Slider controls
    StringCchPrintf(strText, 512, TEXT("%ld"), js.rglSlider[0]);
    SetWindowText(GetDlgItem(hDlg, IDC_SLIDER0), strText);
    StringCchPrintf(strText, 512, TEXT("%ld"), js.rglSlider[1]);
    SetWindowText(GetDlgItem(hDlg, IDC_SLIDER1), strText);

    // Points of view
    StringCchPrintf(strText, 512, TEXT("%ld"), js.rgdwPOV[0]);
    SetWindowText(GetDlgItem(hDlg, IDC_POV0), strText);
    StringCchPrintf(strText, 512, TEXT("%ld"), js.rgdwPOV[1]);
    SetWindowText(GetDlgItem(hDlg, IDC_POV1), strText);
    StringCchPrintf(strText, 512, TEXT("%ld"), js.rgdwPOV[2]);
    SetWindowText(GetDlgItem(hDlg, IDC_POV2), strText);
    StringCchPrintf(strText, 512, TEXT("%ld"), js.rgdwPOV[3]);
    SetWindowText(GetDlgItem(hDlg, IDC_POV3), strText);


    // Fill up text with which buttons are pressed
    StringCchCopy(strText, 512, TEXT(""));
    for (int i = 0; i < 128; i++) {
        if (js.rgbButtons[i] & 0x80) {
            TCHAR sz[128];
            StringCchPrintf(sz, 128, TEXT("%02d "), i);
            StringCchCat(strText, 512, sz);
        }
    }

    SetWindowText(GetDlgItem(hDlg, IDC_BUTTONS), strText);

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: FreeDirectInput()
// Desc: Initialize the DirectInput variables.
//-----------------------------------------------------------------------------
VOID FreeDirectInput() {
    // Unacquire the device one last time just in case 
    // the app tried to exit while the device is still acquired.
    if (g_pJoystick)
        g_pJoystick->Unacquire();

    // Release any DirectInput objects.
    SAFE_RELEASE(g_pJoystick);
    SAFE_RELEASE(g_pDI);
}



